package com.cmii.sjw.department.control.common;

import com.alibaba.fastjson2.JSON;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.support.atomic.RedisAtomicLong;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * RedisServiceImpl
 *
 * @author yinruidong
 * @date 2019/1/29
 * @description redis缓存操作service组件实现
 */
@Service
@Lazy
public class RedisServiceImpl implements RedisService {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
    private static final int MAX_COUNTER = 9999;
    private static final String REDIS_KEY_PREFIX = "order:no:";

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Override
    public <T> T get(final String key, final Class<T> clz) {
        try {
            if (StringUtils.isBlank(key)) {
                if (logger.isWarnEnabled()) {
                    logger.warn("get redis Cache by key: null.");
                }
                return null;
            }
            String jsonData = redisTemplate.opsForValue().get(key);
            if (StringUtils.isNotBlank(jsonData)) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Get redis Cache, key:{}, value:{}", key, jsonData);
                }
                return JsonUtil.fromStr(jsonData, clz);
            }
        } catch (DataAccessException e) {
            if (logger.isErrorEnabled()) {
                logger.error("get redis data error key:{}, reason:{} ", key, e);
            }
            throw e;
        }
        return null;
    }

    @Override
    public <T> Collection<T> getCollection(String key, Class<T> clz) {
        try {
            if (StringUtils.isBlank(key)) {
                if (logger.isWarnEnabled()) {
                    logger.warn("getCollection redis Cache by key is null");
                }
                return Collections.emptyList();
            }
            String jsonData = redisTemplate.opsForValue().get(key);
            if (StringUtils.isNotBlank(jsonData)) {
                if (logger.isDebugEnabled()) {
                    logger.debug("getCollection redis Cache, key:{}, value:{}", key, jsonData);
                }
                return JsonUtil.getCollectionFromStr(jsonData, clz);
            }
        } catch (DataAccessException e) {
            if (logger.isErrorEnabled()) {
                logger.error("get redis data error key:{} ,reason:{} ", key, e);
            }
            throw e;
        }
        return Collections.emptyList();
    }

    @Override
    public boolean isExist(String key) {
        try {
            boolean isExist = redisTemplate.hasKey(key);
            if (logger.isDebugEnabled()) {
                logger.debug("isExist redis cache, key: {}, exist: {}", key, isExist);
            }
            return isExist;
        } catch (DataAccessException e) {
            if (logger.isErrorEnabled()) {
                logger.error("redis has a problem key : {} ,reason: {} ", key, e);
            }
            throw e;
        }
    }

    @Override
    public boolean delete(String key) {
        try {
            redisTemplate.delete(key);
        } catch (DataAccessException e) {
            if (logger.isErrorEnabled()) {
                logger.error("redis delete key : {} error ,reason: {} ", key, e);
            }
            throw e;
        }
        return true;
    }

    @Override
    public <T> boolean set(final String key, final T value) {
        if (StringUtils.isBlank(key)) {
            if (logger.isWarnEnabled()) {
                logger.warn("set redis cache the null. key: {}, value: {} ", key, value);
            }
            return false;
        }
        String valueJson;
        try {
            valueJson = JsonUtil.toJson(value);
            redisTemplate.opsForValue().set(key, valueJson);
            if (logger.isDebugEnabled()) {
                logger.debug("set redis cache key: {}, value: {}", key, valueJson);
            }
        } catch (DataAccessException e) {
            if (logger.isErrorEnabled()) {
                logger.error("set key:{} error, reason : {} ", key, e);
            }
            throw e;
        }
        return true;
    }

    @Override
    public <T> boolean set(final String key, final T value, final long liveTime, TimeUnit timeUnit) {
        if (StringUtils.isBlank(key) || null == value) {
            if (logger.isWarnEnabled()) {
                logger.warn("set redis cache the null. key: {}, value: {}, exp: {}", key, value, liveTime);
            }
            return false;
        }
        String valueJson = null;
        try {
            valueJson = JsonUtil.toJson(value);
            redisTemplate.opsForValue().set(key, valueJson, liveTime, timeUnit);
            if (logger.isDebugEnabled()) {
                logger.debug("set redis cache key: {}, liveTime: {} , value: {}", key, liveTime, valueJson);
            }
        } catch (DataAccessException e) {
            if (logger.isErrorEnabled()) {
                logger.error("set key:{}, value: {}, error reason: {} ", key, valueJson, e);
            }
            throw e;
        }
        return true;
    }

    @Override
    public <T> boolean set(String key, T value, LocalDateTime expireTime) {
        if (StringUtils.isBlank(key) || null == value) {
            if (logger.isWarnEnabled()) {
                logger.warn("set redis cache the null. key: {}, value: {}, exp: {}", key, value, expireTime);
            }
            return false;
        }
        String valueJson = null;
        try {
            valueJson = JsonUtil.toJson(value);
            LocalDateTime now = LocalDateTime.now();
            if(expireTime.isBefore(now)) {
                return false;
            } else {
                Duration duration = Duration.between(now, expireTime);
                redisTemplate.opsForValue().set(key, valueJson, duration.toMillis(), TimeUnit.MILLISECONDS);
            }
            if (logger.isDebugEnabled()) {
                logger.debug("set redis cache key: {}, endTime: {} , value: {}", key, expireTime, valueJson);
            }
        } catch (DataAccessException e) {
            if (logger.isErrorEnabled()) {
                logger.error("set key:{}, value: {}, error reason: {} ", key, valueJson, e);
            }
            throw e;
        }
        return true;
    }

    @Override
    public <T> boolean setNx(final String key, final T value) {
        String valueJson = null;
        try {
            valueJson = JsonUtil.toJson(value);
            return redisTemplate.opsForValue().setIfAbsent(key, valueJson);
        } catch (DataAccessException e) {
            if (logger.isErrorEnabled()) {
                logger.error("set key:{}, value: {}, error reason: {} ", key, valueJson, e);
            }
            throw e;
        }
    }

    @Override
    public long generate(String key) {
        return 1000 + redisTemplate.opsForValue().increment(key, 1);
    }

    @Override
    public RedisTemplate<String, String> getRedisTemplate() {
        return redisTemplate;
    }

    @Override
    public Boolean setTimeout(String key, TimeUnit timeUnit, Long time) {
        return redisTemplate.expire(key, time, timeUnit);
    }


    @Override
    public String flushDb() {
        return redisTemplate.execute((RedisConnection connection) -> {
            connection.flushDb();
            return "ok";
        });
    }

    @Override
    public Long dbSize() {
        return redisTemplate.execute((RedisConnection connection) -> connection.dbSize());
    }

    @Override
    public <T> boolean sadd(String key, T... values) {

        if (StringUtils.isBlank(key)) {
            if (logger.isWarnEnabled()) {
                logger.warn("redis set add cache key: {}, value: {}", key, values);
            }
            return false;
        }
        try {
            final String[] jsonValues = new String[values.length];
            int i = 0;
            for (T value : values) {
                jsonValues[i++] = JsonUtil.toJson(value);
            }
            redisTemplate.opsForSet().add(key, jsonValues);
            if (logger.isDebugEnabled()) {
                logger.debug("redis set add cache key: {}, value: {}", key, jsonValues);
            }
        } catch (DataAccessException e) {
            if (logger.isErrorEnabled()) {
                logger.error("set key:{} error, reason : {} ", key, e);
            }
            throw e;
        }

        return true;

    }

    @Override
    public <T> boolean zadd(String key, double score, T member) {
        if (StringUtils.isBlank(key)) {
            if (logger.isWarnEnabled()) {
                logger.warn("zadd redis cache the null. key: {}, member: {} ", key, member);
            }
            return false;
        }
        String valueJson;
        try {
            valueJson = JsonUtil.toJson(member);
            redisTemplate.opsForZSet().add(key, valueJson, score);
            if (logger.isDebugEnabled()) {
                logger.debug("zadd redis cache key: {}, value: {}", key, valueJson);
            }
        } catch (DataAccessException e) {
            if (logger.isErrorEnabled()) {
                logger.error("zadd key:{} error, reason : {} ", key, e);
            }
            throw e;
        }

        return true;

    }

    @Override
    public <T> Set<T> zrevrange(String key, long start, long end, Class<T> clz) {
        try {
            if (StringUtils.isBlank(key)) {
                if (logger.isWarnEnabled()) {
                    logger.warn("zrevrange redis Cache by key: null.");
                }
                return Collections.emptySet();
            }
            Set<String> jsonDatas = redisTemplate.opsForZSet().range(key, start, end);
            if (null != jsonDatas && !jsonDatas.isEmpty()) {
                if (logger.isDebugEnabled()) {
                    logger.debug("zrevrange redis Cache, key:{}, value:{}", key, jsonDatas);
                }
                Set<T> collection = new HashSet<>();
                for (String json : jsonDatas) {
                    collection.add(JsonUtil.fromStr(json, clz));
                }
                return collection;
            }
        } catch (DataAccessException e) {
            if (logger.isErrorEnabled()) {
                logger.error("get redis data error key:{} ,reason:{} ", key, e);
            }
            throw e;
        }
        return Collections.emptySet();
    }

    @Override
    public <T> T hget(final String key, final String field, final Class<T> clz) {
        try {
            if (StringUtils.isBlank(key) || StringUtils.isBlank(field)) {
                if (logger.isWarnEnabled()) {
                    logger.warn("hget redis Cache by key: null.");
                }
                return null;
            }
            String jsonData = String.valueOf(redisTemplate.opsForHash().get(key, field));
            if (StringUtils.isNotBlank(jsonData)) {
                if (logger.isDebugEnabled()) {
                    logger.debug("hget redis Cache, key:{}, Filed:{}, value:{}", key, field, jsonData);
                }
                return JsonUtil.fromStr(jsonData, clz);
            }
        } catch (DataAccessException e) {
            if (logger.isErrorEnabled()) {
                logger.error("hget redis data error key : {} , Filed: {}, reason : {} ", key, field, e);
            }
            throw e;
        }
        return null;
    }

    @Override
    public <T> boolean hset(String key, String field, T value) {
        if (StringUtils.isBlank(key) || StringUtils.isBlank(field)) {
            if (logger.isWarnEnabled()) {
                logger.warn("hset redis cache the null. key: {}, field: {}, value: {} ", key, field, value);
            }
            return false;
        }
        String valueJson;
        try {
            valueJson = JsonUtil.toJson(value);
            redisTemplate.opsForHash().put(key, field, valueJson);
            if (logger.isDebugEnabled()) {
                logger.debug("hset redis cache  key: {}, field: {}, value: {} ", key, field, valueJson);
            }
        } catch (DataAccessException e) {
            if (logger.isErrorEnabled()) {
                logger.error("hset key:{} error, reason : {} ", key, e);
            }
            throw e;
        }
        return true;
    }

    @Override
    public <T> Double zscore(String key, T value) {
        Double score = null;
        if (StringUtils.isBlank(key) || null == value) {
            if (logger.isWarnEnabled()) {
                logger.warn("zscore redis cache the null. key: {},  value: {} ", key, value);
            }
            return score;
        }
        String valueJson;
        try {
            valueJson = JsonUtil.toJson(value);
            score = redisTemplate.opsForZSet().score(key, valueJson);
            if (logger.isDebugEnabled()) {
                logger.debug("zscore redis cache  key: {}, value: {}, score: {} ", key, valueJson, score);
            }
        } catch (DataAccessException e) {
            if (logger.isErrorEnabled()) {
                logger.error("zscore key:{} error, reason : {} ", key, e);
            }
            throw e;
        }
        return score;
    }

    @Override
    public <T> Set<T> smembers(String key, final Class<T> clz) {
        Set<T> set = new HashSet<>();
        try {
            if (StringUtils.isBlank(key)) {
                if (logger.isWarnEnabled()) {
                    logger.warn("smembers redis Cache by key: null.");
                }
                return set;
            }
            Set<String> jsonSet = redisTemplate.opsForSet().members(key);
            for (String json : jsonSet) {
                set.add(JsonUtil.fromStr(json, clz));
            }
            return set;
        } catch (DataAccessException e) {
            if (logger.isErrorEnabled()) {
                logger.error("smembers redis data error key : {} , reason : {} ", key, e);
            }
            throw e;
        }
    }

    @Override
    public <T> Long srem(String key, T... values) {
        Long count = 0L;
        try {
            if (StringUtils.isBlank(key)) {
                if (logger.isWarnEnabled()) {
                    logger.warn("srem redis Cache by key: null.");
                }
                return count;
            }
            if (null == values || values.length <= 0) {
                if (logger.isWarnEnabled()) {
                    logger.warn("srem redis Cache values: null.");
                }
                return count;
            }
            final String[] jsonValues = new String[values.length];
            int i = 0;
            for (T value : values) {
                jsonValues[i++] = JsonUtil.toJson(value);
            }
            redisTemplate.opsForSet().add(key, jsonValues);
            count = redisTemplate.opsForSet().remove(key, jsonValues);
            return count;
        } catch (DataAccessException e) {
            if (logger.isErrorEnabled()) {
                logger.error("srem redis data error key : {} , reason : {} ", key, e);
            }
            throw e;
        }
    }

    @Override
    public <T> Long zrem(String key, T... values) {
        Long count = 0L;
        try {
            if (StringUtils.isBlank(key)) {
                if (logger.isWarnEnabled()) {
                    logger.warn("zrem redis Cache by key: null.");
                }
                return count;
            }
            if (null == values || values.length <= 0) {
                if (logger.isWarnEnabled()) {
                    logger.warn("zrem redis Cache values: null.");
                }
                return count;
            }
            final String[] jsonValues = new String[values.length];
            int i = 0;
            for (T value : values) {
                jsonValues[i++] = JsonUtil.toJson(value);
            }
            count = redisTemplate.opsForZSet().remove(key, jsonValues);
            return count;
        } catch (DataAccessException e) {
            if (logger.isErrorEnabled()) {
                logger.error("srem redis data error key:{} , reason:{} ", key, e);
            }
            throw e;
        }
    }

    @Override
    public <T> boolean lpush(String key, T... values) {
        if (StringUtils.isBlank(key)) {
            if (logger.isWarnEnabled()) {
                logger.warn("redis lpush add cache key:{}, value:{}", key, values);
            }
            return false;
        }
        try {
            final String[] jsonValues = new String[values.length];
            int i = 0;
            for (T value : values) {
                jsonValues[i++] = JsonUtil.toJson(value);
            }
            redisTemplate.opsForList().leftPushAll(key, jsonValues);
            if (logger.isDebugEnabled()) {
                logger.debug("redis lpush add cache key:{}, value:{}", key, jsonValues);
            }
        } catch (DataAccessException e) {
            if (logger.isErrorEnabled()) {
                logger.error("redis lpush key:{} error, reason:{} ", key, e);
            }
            throw e;
        }

        return true;

    }

    @Override
    public <T> T rpop(String key, Class<T> clz) {
        if (StringUtils.isBlank(key)) {
            if (logger.isWarnEnabled()) {
                logger.warn("redis rpop cache key: {}", key);
            }
            return null;
        }
        try {
            String jsonData = redisTemplate.opsForList().rightPop(key);
            if (StringUtils.isNotBlank(jsonData)) {
                // 如果不为空
                if (logger.isDebugEnabled()) {
                    logger.debug("Get redis Cache, key: {}, value: {}", key, jsonData);
                }
                return JsonUtil.fromStr(jsonData, clz);
            }
        } catch (DataAccessException e) {
            if (logger.isErrorEnabled()) {
                logger.error("redis rpop key:{} error, reason : {} ", key, e);
            }
            throw e;
        }
        return null;
    }

    @Override
    public boolean hexists(String key, String field) {
        try {
            if (StringUtils.isBlank(key) || StringUtils.isBlank(field)) {
                if (logger.isWarnEnabled()) {
                    logger.warn("hexists redis Cache by key:{} ,field:{} is null.", key, field);
                }
                return false;
            }
            return redisTemplate.opsForHash().hasKey(key, field);
        } catch (DataAccessException e) {
            if (logger.isErrorEnabled()) {
                logger.error("hexists redis data error key : {} , Filed: {}, reason : {} ", key, field, e);
            }
            return false;
        }
    }

    @Override
    public boolean hincrby(String key, String field, Long delta) {
        if (StringUtils.isBlank(key) || StringUtils.isBlank(field)) {
            if (logger.isWarnEnabled()) {
                logger.warn("hincrby redis cache the null. key: {}, field: {}, delta: {} ", key, field, delta);
            }
            return false;
        }
        try {
            redisTemplate.opsForHash().increment(key, field, delta);
            if (logger.isDebugEnabled()) {
                logger.debug("hincrby redis cache  key:{}, field:{}, delta:{} ", key, field, delta);
            }
        } catch (DataAccessException e) {
            if (logger.isErrorEnabled()) {
                logger.error("hincrby key:{} error, reason:{} ", key, e);
            }
            throw e;
        }
        return true;
    }

    @Override
    public Long hlen(String key) {
        Long hlen = 0L;
        if (StringUtils.isBlank(key)) {
            if (logger.isWarnEnabled()) {
                logger.warn("hlen redis cache the null. key:{}", key);
            }
            return hlen;
        }
        try {
            hlen = redisTemplate.opsForHash().size(key);
            if (logger.isDebugEnabled()) {
                logger.debug("hlen redis cache key:{}, size:{} ", key, hlen);
            }
        } catch (DataAccessException e) {
            if (logger.isErrorEnabled()) {
                logger.error("hlen key:{} error, reason:{} ", key, e);
            }
            throw e;
        }
        return hlen;
    }

    /**
     * 从redis中获取key对应的过期时间;
     * 如果该值有过期时间，就返回相应的过期时间;
     * 如果该值没有设置过期时间，就返回-1;
     * 如果没有该值，就返回-2;
     *
     * @param key
     * @return
     */
    @Override
    public Long getResidueTime(String key) {
        Long time = 0L;
        try {
            time = redisTemplate.opsForValue().getOperations().getExpire(key);
            if (logger.isDebugEnabled()) {
                logger.debug("getResidueTime redis cache key:{}, time:{} ", key, time);
            }
        } catch (DataAccessException e) {
            if (logger.isErrorEnabled()) {
                logger.error("getResidueTime key:{} error, reason:{} ", key, e);
            }

        }
        return time;
    }

    @Override
    public <T> boolean setNx(final String key, final T value,long time, TimeUnit timeUnit) {
        String valueJson = null;
        try {
            valueJson = JsonUtil.toJson(value);
            return redisTemplate.opsForValue().setIfAbsent(key, valueJson,time,timeUnit);
        } catch (DataAccessException e) {
            if (logger.isErrorEnabled()) {
                logger.error("set key:{}, value: {}, error reason: {} ", key, valueJson, e);
            }
            throw e;
        }
    }

    /**
     * 生成订单号
     * @param bizPrefix 业务前缀
     * @return
     */
    @Override
    public String orderNoGenerator(String bizPrefix) {
        if (bizPrefix == null || bizPrefix.length() != 2) {
            throw new IllegalArgumentException("业务前缀必须是2位大写字母");
        }

        String timestamp = LocalDateTime.now().format(FORMATTER);
        String redisKey = REDIS_KEY_PREFIX + timestamp;

        // 使用Redis原子计数器
        RedisAtomicLong counter = new RedisAtomicLong(
                redisKey,
                Objects.requireNonNull(redisTemplate.getConnectionFactory())
        );

        // 设置过期时间为1天
        counter.expire(1, TimeUnit.DAYS);

        long sequence = counter.incrementAndGet();
        if (sequence > MAX_COUNTER) {
            // 如果超过最大值，重置为1
            counter.set(1);
            sequence = 1;
        }

        return String.format("%s%s%04d",
                bizPrefix.toUpperCase(),
                timestamp,
                sequence);
    }

    /**
     * 获取缓存
     * @param key 缓存key
     * @return 缓存值
     */
    @Override
    public <T> T getCache(String key, Class<T> clazz) {
        String value = redisTemplate.opsForValue().get(key);
        return StringUtils.isNotBlank(value) ? JSON.parseObject(value, clazz) : null;
    }

    /**
     * 设置缓存
     * @param key 缓存key
     * @param value 缓存值
     * @param timeout 过期时间(秒)
     */
    @Override
    public void setCache(String key, Object value, long timeout) {
        redisTemplate.opsForValue().set(key, JSON.toJSONString(value), timeout, TimeUnit.SECONDS);
    }
}
